<?php
/**
 * Clover Coverage Report class file
 *  
 * @package Apricot
 */

/**
 * Clover Coverage Report
 *
 * This class parses a clover coverage XML file and generates a report of the
 * findings in ASCII format.
 * 
 * @package Apricot
 * @author Jansen Price <jansen.price@gmail.com>
 * @version $Id$
 */
class CloverCoverageReport
{
    /**
     * Xml data
     * 
     * @var mixed
     */
    protected $_xml = null;

    /**
     * Report text
     * 
     * @var string
     */
    protected $_reportText = '';

    /**
     * Files
     * 
     * @var array
     */
    protected $_files = array();

    /**
     * Constructor
     * 
     * @param string $xmlFilename Filename to XML file
     * @param string $targetFile Optional PHP file for report
     * @return void
     */
    public function __construct($xmlFilename, $targetFile = '')
    {
        if (!file_exists($xmlFilename)) {
            throw new Exception(
                "File '$xmlFilename' not found or is not readable.", 64
            );
        }
        $this->_xml = simplexml_load_file($xmlFilename);

        if ($targetFile) {
            $this->generateFileAnalysis($targetFile);
        } else {
            $this->generateReport();
        }
        $this->render();
    }

    /**
     * Run the logic to generate the report
     * 
     * @return void
     */
    public function generateReport()
    {
        $this->gatherFileMetrics();
        $root = $this->findCommonRoot(array_keys($this->_files));

        $timestamp = (int) $this->_xml->project['timestamp'];

        $this->addTitle();
        $this->append(
            "Coverage report generated " . date('Y-m-d H:i:s', $timestamp)
        );
        $this->append("Root: " . $root);
        $this->append(str_repeat("-", 64));
        $this->addFileMetrics($root);
    }

    /**
     * Gather file metrics from XML
     * 
     * @return void
     */
    public function gatherFileMetrics()
    {
        foreach ($this->_xml->project->file as $file) {
            $name = (string) $file['name'];

            $coveredStatements = (int) $file->metrics['coveredstatements'];

            $fileMetric = array(
                'coveredstatements' => $coveredStatements,
                'statements' => (int) $file->metrics['statements'],
            );

            $this->_files[$name] = $fileMetric;
        }
    }

    /**
     * Add file metrics to report text
     * 
     * @param string $root The path root
     * @return void
     */
    public function addFileMetrics($root = '')
    {
        ksort($this->_files);
        $longestNameLength = 10;
        $largestLineCount  = 2;

        foreach ($this->_files as $name => $metrics) {
            $newFiles[str_replace($root, '', $name)] = $metrics;
        }
        $this->_files = $newFiles;

        foreach ($this->_files as $name => $metrics) {
            if (strlen($name) > $longestNameLength) {
                $longestNameLength = strlen($name);
            }

            if ($metrics['statements'] > $largestLineCount) {
                $largestLineCount = $metrics['statements'];
            }
        }

        $lineCountPad = strlen((string) $largestLineCount);

        foreach ($this->_files as $name => $metrics) {
            $line = str_pad($name, $longestNameLength);

            $coveredStatements = str_pad(
                $metrics['coveredstatements'], $lineCountPad, ' ', STR_PAD_LEFT
            );

            $statements = str_pad(
                $metrics['statements'], $lineCountPad, ' ', STR_PAD_LEFT
            );

            $percent = round(
                $metrics['coveredstatements'] / $metrics['statements'] * 100
            );

            $line .= " | " . $coveredStatements . ' / ' . $statements
                . " | " . str_pad($percent, 3, ' ', STR_PAD_LEFT) . "%"
                . "  " . $this->_bar($percent);

            $this->append($line);
        }
    }

    /**
     * Generate file analysis
     * 
     * @param string $file Filename
     * @return void
     */
    public function generateFileAnalysis($file)
    {
        $this->gatherFileMetrics();
        $root = $this->findCommonRoot(array_keys($this->_files));

        // If file isn't in XML, prepend the root
        if (!isset($this->_files[$file])) {
            $file = $root . $file;
        }

        // If it still isn't in the list, we don't know about it. abort.
        if (!isset($this->_files[$file])) {
            throw new Exception("Unknown file $file.");
        }

        $stats = $this->gatherFileStatistics($file);

        $lines = file($file);

        foreach ($lines as $index => $line) {
            $lineNumber = $index + 1;

            $prepend = str_pad($lineNumber, 5, ' ', STR_PAD_LEFT) . " ";

            if (isset($stats[$lineNumber])) {
                $prepend .= str_pad(
                    $stats[$lineNumber]->count, 8, ' ', STR_PAD_LEFT
                ) . " : ";
            } else {
                $prepend .= str_repeat(' ', 8) . " : ";
            }
            $this->append($prepend . rtrim($line));
        }
    }

    /**
     * Gather the line statistics for a given PHP file
     * 
     * @param string $filename Filename
     * @return array
     */
    public function gatherFileStatistics($filename)
    {
        $stats = array();

        foreach ($this->_xml->project->file as $file) {
            if ((string) $file['name'] == $filename) {
                break;
            }
        }

        foreach ($file->line as $line) {
            $lineObject = new StdClass();
            foreach ($line->attributes() as $key => $value) {
                $lineObject->{$key} = (string) $value;
            }
            $lineNumber = (int) $line['num'];

            $stats[$lineNumber] = $lineObject;
        }

        return $stats;
    }

    /**
     * Find common root from a list of file paths
     * 
     * @param array $list A list of file paths
     * @return string
     */
    public function findCommonRoot($list)
    {
        $longest = 0;

        foreach ($list as $item) {
            if (strlen($item) > $longest) {
                $longest = strlen($item);
            }
        }

        for ($i = 1; $i < $longest; $i++) {
            $common = array();
            foreach ($list as $item) {
                $common[] = substr($item, 0, $i);
            }

            if (count(array_unique($common)) > 1) {
                return substr($common[0], 0, -1);
            }
        }
    }

    /**
     * Create bar
     * 
     * @param mixed $percent The percent value
     * @return string
     */
    protected function _bar($percent)
    {
        $width = 10;
        $val   = $percent / $width;

        $text = "["
            . str_pad(str_repeat("*", $val), $width)
            . "]";

        return $text;
    }

    /**
     * Render the report
     * 
     * @return void
     */
    public function render()
    {
        echo $this->_reportText;
    }

    /**
     * Add the title of the report (project name)
     * 
     * @return void
     */
    public function addTitle()
    {
        $project = $this->_xml->project;

        $name = (string) $project['name'];

        $this->append(str_repeat("-", 64));
        $this->append($name);
        $this->append(str_repeat("-", 64));
    }

    /**
     * Append text to the report text accumulator
     * 
     * @param mixed $text Text to append
     * @return void
     */
    public function append($text)
    {
        $this->_reportText .= $text . "\n";
    }
}
